using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace DejaVuTocReader
{
    
    /**
 * This Enumeration iterates over a CachedInputStream as an ordered list of
 * CachedInputStream's.  The chunk id's are stored in the CachedInputStream
 * as the name value.
 * 
 * IFF files were originally intended for audio.  But is well suited for any 
 * streaming data.
 */
    public class IFFEnumeration
    {
      //~ Static fields/initializers ---------------------------------------------

      /** chunk names which can contain other chunks */
      static String[] szComposite = {"FORM", "LIST", "PROP", "CAT "};
      /** chunk names which should not be used */
      static String[] szReserved = {"FOR", "LIS", "CAT"};

      //~ Instance fields --------------------------------------------------------

      /** The raw input stream. */
      protected CachedInputStream input;

      /** The next CachedInputStream object on the list */
      protected CachedInputStream next=null;

      // Buffer used for opening the chunk
      private byte[] bufA = new byte[4];
      
      // Buffer used for opening the chunk
      private byte[] bufB = new byte[4];
      
      //~ Constructors -----------------------------------------------------------

      /**
       * Creates a new IFFEnumeration object.
       */
      public IFFEnumeration() {}

      //~ Methods ----------------------------------------------------------------


     

      /**
       * Initialize this stream.
       *
       * @param input data source
       *
       * @return the initialized stream
       */
      public IFFEnumeration init(CachedInputStream input)
      {
        this.input = (CachedInputStream)input.clone();
        return this;
      }

      /**
       * Query if there are more CachedInputStream's available.
       *
       * @return True if there is another CachedInputStream available.
       */
      public bool hasMoreElements()
      {
        if(next == null)
        {
          try
          {
            next=openChunk();
          }
          catch(IOException) 
          {
            
            next=null;   
          }
        }
        return (next != null);
      }
      
      /**
       * Query the next CachedInputStream.
       *
       * @return The next CachedInputStream.
       * 
       * @throws NoSuchElementException if there are no more streams available.
       */
      public Object nextElement()
      {
        if(!hasMoreElements())
        {
          throw new IOException("EOF");
        }
        CachedInputStream retval=next;
        next=null;
        return retval;
      }
      
      /*
       * Get the next chunk id.
       *
       * @return the newly created CachedInputStream
       *
       * @throws IOException if an error occurs
       */
      private CachedInputStream openChunk()
      {

        do
        {
          if(input.read(bufA) < 4)
          {
            return null;
          }
        }
        while(
          (bufA[0] == 65)
          && (bufA[1] == 84)
          && (bufA[2] == 38)
          && (bufA[3] == 84));
        
        if(input.read(bufB) < 4)
        {
          return null;
        }
        int size =
          ((0xff & bufB[0]) << 24) | ((0xff & bufB[1]) << 16)
          | ((0xff & bufB[2]) << 8) | (0xff & bufB[3]);
        if(size < 0)
        {
          return null;
        }
        String id1=genId(bufA);
        String id2=null;
        if(check_id(id1))
        {
          if(size < 4)
          {
            return null;
          }
          if(input.read(bufB) < 4)
          {
            return null;
          }
          size-=4;
          id2=genId(bufB);
          check_id(id2);
        }
        CachedInputStream retval=input.createCachedInputStream(size);
        retval.setName((id2 != null)?(id1+":"+id2):id1);
        input.skip(size+(size&1));
        return retval;
      }

      /*
       * Read the an id.
       *
       * @param data byte array to read
       *
       * @return the id string
       */
      private static String genId(byte[] data)
      {
          MemoryStream ms = new MemoryStream(data);
        return ((data != null) && (data.Length > 0))
        ? ((new StreamReader(ms,Encoding.UTF8)).ReadToEnd() + "\0\0\0\0").Substring(0, 4)
        : "\0\0\0\0";
      }
      
      /**
       * Check for the specified id
       *
       * @param id id to check for
       *
       * @return true if the id was found
       *
       * @throws IOException if an error occurs
       */
      public static bool check_id(String id)
      {
        for(int i = 0; i < 4; i++)
        {
          if((id[i] < ' ') || (id[i] > '~'))
          {
            throw new IOException("Illegal chunk id");
          }
        }
        for(int i = 0; i < szComposite.Length; i++)
        {
          if(id.Equals(szComposite[i]))
          {
            return true;
          }
        }
        String s = id.Substring(0, 3);
        for(int i = 0; i < szReserved.Length; i++)
        {
          if(
            s.Equals(szReserved[i])
            && (id[3] >= '1')
            && (id[3] <= '9'))
          {
            throw new IOException("Illegal chunk id");
          }
        }
        return false;
      }
    }




}
